/*
 * FileAsMsg plug-in for Miranda IM and QIP Infium.
 * Sending/receiving files via instant messaging without protocol support.
 * Homepage: http://code.google.com/p/fileasmsg/
 *
 * Copyright (c) 2008, Radist <radist.nt@gmail.com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Name(s) of contributor(s) may not be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY Radist ''AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL Radist BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "coder.h"
#include "TransferFile.h"
#include <string.h>
#include "plugin.h"

extern "C" unsigned char Crc8(const unsigned char *pcBlock, unsigned char len);

//////////////////////////////////////////////////////////////////////////
// Template for completing CoderIf
//
// Requires 3 additional _inline classes:
//  _Enc: encoder (Encode, Decode)
//  _Chh: chunk hasher (Hash)
//  _Hsr: file hasher (Reset, Add, Finish)

template<class _Enc, class _Chh, class _Hsr>
class CombCoder
	: public CoderIf, private _Enc, private _Chh, private _Hsr
{
	CombCoder(): CoderIf(NULL) {}
protected:
	virtual const char *HashPiece(const unsigned char*data, size_t datalen);
	virtual void ResetHashing(void);
	virtual const char *FinishHash(void);
	virtual bool EncodeData(char*str, size_t strsize, const unsigned char*data, size_t datalen);
	virtual size_t DecodeData(unsigned char*data, size_t datasize, const char*str);
public:
	CombCoder(TransferFile*f, DWORD flags);
	virtual ~CombCoder();
	virtual void AddHashPart(const unsigned char*data, size_t datalen);
};

template<class _Enc, class _Chh, class _Hsr>
CombCoder<_Enc, _Chh, _Hsr>::CombCoder( TransferFile*f, DWORD flags )
: CoderIf(f, flags)
{
}

template<class _Enc, class _Chh, class _Hsr>
CombCoder<_Enc, _Chh, _Hsr>::~CombCoder()
{
}

template<class _Enc, class _Chh, class _Hsr>
const char *CombCoder<_Enc, _Chh, _Hsr>::HashPiece(const unsigned char*data, size_t datalen)
{
	return _Chh::Hash(data, datalen);
}

template<class _Enc, class _Chh, class _Hsr>
void CombCoder<_Enc, _Chh, _Hsr>::ResetHashing( void )
{
	_Hsr::Reset();
}

template<class _Enc, class _Chh, class _Hsr>
void CombCoder<_Enc, _Chh, _Hsr>::AddHashPart( const unsigned char*data, size_t datalen )
{
	_Hsr::Add(data, datalen);
}

template<class _Enc, class _Chh, class _Hsr>
const char * CombCoder<_Enc, _Chh, _Hsr>::FinishHash( void )
{
	return _Hsr::Finish();
}

template<class _Enc, class _Chh, class _Hsr>
bool CombCoder<_Enc, _Chh, _Hsr>::EncodeData( char*str, size_t strsize, const unsigned char*data, size_t datalen )
{
	return _Enc::Encode(str, strsize, data, datalen);
}

template<class _Enc, class _Chh, class _Hsr>
size_t CombCoder<_Enc, _Chh, _Hsr>::DecodeData( unsigned char*data, size_t datasize, const char*str )
{
	return _Enc::Decode(data, datasize, str);
}

//////////////////////////////////////////////////////////////////////////
// Base64 data encoder

static const char b64enc[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

class Base64_Enc
{
	unsigned char DecCh(char ch)
	{
		if (ch>='A')
		{
			if (ch<='Z')
			{
				return ch - 'A';
			} 
			else if (ch<='z'&&ch>='a')
			{
				return ch - 'a' + 26;
			}
		} 
		else if (ch>='0')
		{
			if (ch<='9')
			{
				return ch - '0' + 52;
			}
		}
		else
		{
			switch (ch)
			{
			case '+':
				return 62;
			case '/':
				return 63;
			}
		}
		throw NULL;
	}
public:
	bool Encode(char*str, size_t strsize, const unsigned char*data, size_t datalen)
	{
		if (strsize <= ((datalen+2)/3)*4)
		{
			return false;
		}
		unsigned __int32 c;
		for (size_t i = datalen/3; i--; data += 3, str += 4)
		{
			c = *data<<16|*(data+1)<<8|*(data+2);
			str[0] = b64enc[c>>18];
			str[1] = b64enc[c>>12&63];
			str[2] = b64enc[c>>6&63];
			str[3] = b64enc[c&63];
		}
		if (datalen %= 3)
		{
			c = *data<<8;
			if (2 == datalen)
			{
				c |= *(data+1);
				*(str++) = b64enc[c>>10];
				c = c<<6&0xFFFF;
			}
			*(str++) = b64enc[c>>10];
			*(str++) = b64enc[c>>4&63];
			*(str++) = '=';
			if (1 == datalen)
			{
				*(str++) = '=';
			}
		}
		*str = 0;
		return true;
	}
	size_t Decode(unsigned char*data, size_t datasize, const char*str)
	{
		size_t strsize = strlen(str);
		if (strsize&3/*strsize%4*/ || strsize > ((datasize+2)/3)*4)
		{
			return 0;
		}
		else if (((datasize+2)/3)*4==strsize)
		{
			switch (datasize%3)
			{
			case 1: if ('='!=str[strsize-2]) return 0;
			case 2: if ('='!=str[strsize-1]) return 0;
			}
		}
		datasize = strsize/4*3;
		if ('='==str[strsize-1])
		{
			--datasize;
			if ('='==str[strsize-2]) --datasize;
		}
		try
		{
			unsigned __int32 c;
			for (size_t i = datasize/3; i--; data += 3, str += 4)
			{
				c = DecCh(*str)<<18;
				c |= DecCh(str[1])<<12;
				c |= DecCh(str[2])<<6;
				c |= DecCh(str[3]);
				data[0] = c>>16;
				data[1] = c>>8&0xFF;
				data[2] = c&0xFF;
			}
			if (datasize%3)
			{
				c = DecCh(*str)<<10;
				c |= DecCh(str[1])<<4;
				data[0] = c>>8;
				if (2==datasize%3)
				{
					c |= DecCh(str[2])>>2;
					data[1] = c&0xFF;
				}
			}
			return datasize;
		}
		catch (...)
		{
			return 0;
		}
	}
};

//////////////////////////////////////////////////////////////////////////
// byte -> hex converter

static const char hex_code[] = "0123456789abcdef";
template<size_t len>inline void Byte2Hex(char *buf)
{
	buf += len;
	for (char *ch = buf + len; --ch > --buf;)
	{
		*ch = hex_code[(unsigned char)(*buf)>>4];
		*--ch = hex_code[(*buf)&0xf];
	}
}

//////////////////////////////////////////////////////////////////////////
// CRC8 chunk hasher

class CRC8_Chh
{
	char crc[3];
public:
	CRC8_Chh()
	{
		crc[sizeof(crc)-1] = 0;
	}
	const char *Hash(const unsigned char*data, size_t datalen)
	{
		*crc = Crc8(data, datalen);
		Byte2Hex<1>(crc);
		return crc;
	}
};

//////////////////////////////////////////////////////////////////////////
// Empty chunk hasher

class None_Chh
{
public:
	const char *Hash(const unsigned char*, size_t)
	{
		return "";
	}
};

//////////////////////////////////////////////////////////////////////////
// SHA1 file hasher

class SHA1_Hsr
{
	char sha1[2*SHA1_HASH_SIZE+1];
	SHA1_CTX ctx;
public:
	SHA1_Hsr()
	{
		sha1[sizeof(sha1)-1] = 0;
		Reset();
	}
	void Reset(void)
	{
		sha1_init(&ctx);
	}
	void Add(const unsigned char*data, size_t datalen)
	{
		sha1_append(&ctx, (unsigned char*)data, datalen);
	}
	const char *Finish(void)
	{
		sha1_finish(&ctx, (unsigned char*)sha1);
		Byte2Hex<SHA1_HASH_SIZE>(sha1);
		return sha1;
	}
};

//////////////////////////////////////////////////////////////////////////
//CoderIf

CoderIf::CoderIf(TransferFile*_f, DWORD _flags)
: file(_f), next_chunk(0), do_rehash(false), fhash(NULL), desiredhash(NULL), flags(_flags)
{
}

CoderIf::~CoderIf() /*=0*/
{
	if (desiredhash)
	{
		_free(desiredhash);
	}
}

void CoderIf::NotifyHash( const char*_hash )
{
	if (desiredhash)
	{
		_free(desiredhash);
	}
	desiredhash = _strdup(_hash);
}

bool CoderIf::HashNitified( void )
{
	return NULL!=desiredhash;
}

bool CoderIf::CheckHash( void )
{
	return !strcmp(GetHash(), desiredhash);
}

const char* CoderIf::GetHash( void )
{
	if (fhash)
	{
		return fhash;
	}
	if (do_rehash)
	{
		ResetHashing();
		file->RehashFile();
	}
	return fhash = FinishHash();
}

#define HASH_PIECE_DELIMETER ':'

size_t CoderIf::Encode( char*msg, size_t msgsize, const unsigned char*data, size_t datasize )
{
	const char *h = HashPiece(data, datasize);
	AddHashPart(data, datasize);
	size_t hl = strlen(h);
	if ((msgsize-=hl+1)<=1)
	{
		return 0;
	}
	memcpy(msg, h, hl);
	*(msg+=hl) = HASH_PIECE_DELIMETER;
	return EncodeData(++msg, msgsize, data, datasize)? hl + 1 + strlen(msg) : 0;
}

bool CoderIf::Decode( unsigned char*data, size_t datasize, const char*msg, DWORD chunk_no )
{
	const char *d = strchr(msg, HASH_PIECE_DELIMETER);
	if (!d || !*++d ||
		DecodeData(data, datasize, d)!=datasize ||
		strncmp(msg, HashPiece(data, datasize), d - msg - 1))
	{
		return false;
	}
	if (!(do_rehash|=(next_chunk++!=chunk_no)))
	{
		AddHashPart(data, datasize);
	}
	return true;
}

//Factory method
CoderIf * CoderIf::CreateCoderIf( TransferFile*file, DWORD flags )
{
	return new CombCoder<Base64_Enc, CRC8_Chh, SHA1_Hsr>(file, flags);
}
